#!/usr/bin/env python
# -*- coding: utf-8 -*-

import os
import sys
import re
import json
import shutil
import subprocess
from datetime import datetime

# 配置
PROTO_DIR = "proto"
OUTPUT_DIR = "Assets/Scripts/ProtoGen"
EXCLUDED_MODULES = ["server"]
PROTOC_PATH = "protoc-31.0-rc-2-win64/bin/protoc.exe"
MAPPING_FILE = os.path.join(OUTPUT_DIR, "ProtocolMappings.json")

# 调试模式
DEBUG = True

# 编译日志
compilation_log = []

def log_info(message):
    """记录信息日志"""
    print(f"[信息] {message}")
    compilation_log.append(f"[信息] {message}")

def log_error(message):
    """记录错误日志"""
    print(f"[错误] {message}")
    compilation_log.append(f"[错误] {message}")

def log_debug(message):
    """记录调试日志"""
    if DEBUG:
        print(f"[调试] {message}")
        compilation_log.append(f"[调试] {message}")

def ensure_dir(directory):
    """确保目录存在"""
    if not os.path.exists(directory):
        os.makedirs(directory)
        log_info(f"创建目录: {directory}")

def clean_excluded_dirs():
    """清理排除模块的输出目录"""
    for module in EXCLUDED_MODULES:
        excluded_dir = os.path.join(OUTPUT_DIR, module)
        if os.path.exists(excluded_dir):
            shutil.rmtree(excluded_dir)
            log_info(f"已删除排除模块的输出目录: {excluded_dir}")

def compile_proto_file(proto_path, output_dir):
    """编译单个proto文件"""
    log_info(f"编译: {proto_path} -> {output_dir}")
    
    cmd = [
        os.path.join(os.getcwd(), PROTOC_PATH),
        f"--proto_path=.",
        f"--proto_path={PROTO_DIR}",
        f"--proto_path=protoc-31.0-rc-2-win64/include",
        f"--csharp_out={output_dir}",
        proto_path
    ]
    
    try:
        result = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
        if result.stdout:
            log_info(result.stdout)
        if result.stderr:
            log_error(f"编译 {proto_path} 错误: {result.stderr}")
        if result.returncode != 0:
            log_error(f"编译失败，退出代码: {result.returncode}")
            return False
        return True
    except Exception as e:
        log_error(f"执行编译命令时出错: {e}")
        return False

def extract_module_name(proto_path):
    """从proto文件路径提取模块名"""
    # 将路径标准化为使用正斜杠
    proto_path = proto_path.replace('\\', '/')
    
    # 获取相对于proto目录的路径
    if PROTO_DIR in proto_path:
        parts = proto_path.split('/')
        proto_dir_index = -1
        
        # 查找proto_dir在路径中的位置
        for i, part in enumerate(parts):
            if part == PROTO_DIR:
                proto_dir_index = i
                break
        
        if proto_dir_index >= 0 and len(parts) > proto_dir_index + 1:
            if len(parts) > proto_dir_index + 2:  # 有子目录
                # 返回子目录名作为模块名
                return parts[proto_dir_index + 1]
    
    # 如果不在子目录中，则使用文件名作为模块名（不含扩展名）
    filename = os.path.basename(proto_path)
    return os.path.splitext(filename)[0]

def parse_proto_file(proto_path):
    """解析proto文件，提取消息名和cmd值"""
    module = extract_module_name(proto_path)
    log_debug(f"提取模块名: {proto_path} -> {module}")
    
    # 检查模块是否被排除
    if module.lower() in [m.lower() for m in EXCLUDED_MODULES]:
        log_info(f"跳过解析排除的模块: {module}")
        return [], module
    
    log_info(f"解析文件: {proto_path}, 模块: {module}")
    
    # 读取proto文件内容
    try:
        with open(proto_path, 'r', encoding='utf-8') as f:
            content = f.read()
    except Exception as e:
        log_error(f"读取文件 {proto_path} 失败: {e}")
        return [], module
    
    # 正则表达式提取message块
    message_blocks = re.finditer(r'message\s+(\w+)\s*\{([^}]*)\}', content, re.DOTALL)
    
    messages = []
    message_count = 0
    
    for match in message_blocks:
        message_count += 1
        message_name = match.group(1)
        message_body = match.group(2)
        
        log_debug(f"找到消息: {message_name} in {proto_path}")
        
        # 查找cmd选项
        cmd_match = re.search(r'option\s*\(\s*(?:com\.lin\.proto\.)?(cmd)\s*\)\s*=\s*(-?\d+)', message_body)
        if cmd_match:
            cmd_value = int(cmd_match.group(2))
            is_request = not (message_name.startswith("Res") or "Response" in message_name)
            
            messages.append({
                "name": message_name,
                "cmd": cmd_value,
                "module": module,
                "is_request": is_request
            })
            
            log_info(f"解析协议: {message_name}, CMD: {cmd_value}, 模块: {module}")
        else:
            log_debug(f"消息 {message_name} 没有cmd选项")
    
    if message_count > 0 and len(messages) == 0:
        log_info(f"文件 {proto_path} 包含 {message_count} 个消息，但没有找到带cmd的协议")
    
    return messages, module

def find_all_proto_files():
    """递归查找所有proto文件"""
    proto_files = []
    excluded_dirs = [os.path.join(PROTO_DIR, module.lower()) for module in EXCLUDED_MODULES]
    
    for root, dirs, files in os.walk(PROTO_DIR):
        # 移除排除的目录，避免遍历
        dirs_to_remove = []
        for i, dir_name in enumerate(dirs):
            dir_path = os.path.join(root, dir_name)
            if any(dir_path.lower() == excl_dir.lower() for excl_dir in excluded_dirs):
                log_info(f"跳过排除的目录: {dir_path}")
                dirs_to_remove.append(dir_name)
        
        # 从dirs列表中移除排除的目录
        for dir_name in dirs_to_remove:
            dirs.remove(dir_name)
        
        # 查找proto文件
        for file in files:
            if file.endswith(".proto"):
                full_path = os.path.join(root, file)
                proto_files.append(full_path)
                log_debug(f"发现proto文件: {full_path}")
    
    log_info(f"找到 {len(proto_files)} 个proto文件")
    return proto_files

def compile_proto_files():
    """编译所有proto文件"""
    # 确保输出目录存在
    ensure_dir(OUTPUT_DIR)
    
    # 检查protoc编译器是否存在
    protoc_full_path = os.path.join(os.getcwd(), PROTOC_PATH)
    if not os.path.exists(protoc_full_path):
        log_error(f"找不到protoc编译器: {protoc_full_path}")
        return None
    
    log_info(f"使用protoc: {protoc_full_path}")
    
    # 清理排除模块的旧输出目录
    clean_excluded_dirs()
    
    all_messages = []
    all_modules = set()
    
    # 查找所有proto文件
    proto_files = find_all_proto_files()
    
    # 编译和解析所有proto文件
    compiled_count = 0
    for proto_path in proto_files:
        # 确定输出目录
        relative_path = os.path.relpath(proto_path, PROTO_DIR)
        module_dir = os.path.dirname(relative_path)
        
        output_dir = OUTPUT_DIR
        if module_dir:
            output_dir = os.path.join(OUTPUT_DIR, module_dir)
            ensure_dir(output_dir)
        
        # 编译proto文件
        if compile_proto_file(proto_path, output_dir):
            compiled_count += 1
            # 解析proto文件
            messages, module = parse_proto_file(proto_path)
            all_messages.extend(messages)
            if module and module.lower() not in [m.lower() for m in EXCLUDED_MODULES]:
                all_modules.add(module)
    
    log_info(f"成功编译 {compiled_count}/{len(proto_files)} 个proto文件")
    log_info(f"找到 {len(all_messages)} 个协议定义")
    return all_messages, all_modules

def generate_mapping_file(messages, modules):
    """生成协议映射文件"""
    log_info("生成协议号映射文件...")
    
    # 创建协议映射
    mapping = {
        "version": "1.0",
        "protocols": {},
        "moduleMap": {}
    }
    
    # 填充protocols
    for msg in messages:
        cmd_key = str(msg["cmd"])
        mapping["protocols"][cmd_key] = {
            "name": msg["name"],
            "module": msg["module"],
            "description": f"table {msg['name']}(cmd:{msg['cmd']})" + "{",
            "isRequest": msg["is_request"]
        }
    
    # 按字母顺序排序模块名
    sorted_modules = sorted(modules)
    
    # 填充moduleMap (按排序后的顺序)
    for module in sorted_modules:
        mapping["moduleMap"][module] = {
            "protocols": []
        }
    
    # 将协议添加到对应模块
    for msg in messages:
        module = msg["module"]
        if module in mapping["moduleMap"]:
            mapping["moduleMap"][module]["protocols"].append(msg["cmd"])
    
    # 保存到文件 - 使用有序的JSON格式
    ensure_dir(os.path.dirname(MAPPING_FILE))
    with open(MAPPING_FILE, 'w', encoding='utf-8') as f:
        json.dump(mapping, f, indent=2, ensure_ascii=False, sort_keys=False)
    
    log_info(f"协议号映射文件已保存: {MAPPING_FILE}")

def main():
    """主函数"""
    print("========== Proto编译工具 ==========")
    log_info("开始编译Proto文件...")
    
    try:
        # 编译proto文件
        result = compile_proto_files()
        if result is None:
            log_error("编译失败")
            return
        
        messages, modules = result
        
        # 生成映射文件
        generate_mapping_file(messages, modules)
        
        log_info("编译完成！")
    except Exception as e:
        log_error(f"执行过程中出错: {e}")
        import traceback
        log_error(traceback.format_exc())
    
    print("\n按回车键退出...")
    input()

if __name__ == "__main__":
    main() 